/**
 * @file videoDecoder.h
 * @author SDK_TEAM
 * @brief
 * @version 0.1
 * @date 2022-11-09
 *
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 * This file can not be copied or distributed without the permission of Lynxi
 * Technologies Co., Ltd.
 *
 */
#pragma once

#include <lyn_api.h>

#include <atomic>
#include <cmath>
#include <future>
#include <sstream>
#include <string>
#include <thread>

#include "blockQueue.hpp"
#include "framePool.hpp"
#include "util.hpp"

// 解码数据结构体，用于解码回调函数内进行数据处理
typedef struct {
  lynFrame_t *pFrame;  // 解码后的数据
  BlockQueue<lynFrame_t *> *pFrameQueue;
} DECODE_DATA_T;

enum InputType { File, Stream };

// 视频解码类
class VideoDecoder {
 public:
  VideoDecoder() : m_pThread(nullptr) 
  {
    m_bQuit = false;
  }

  // 根据视频路径初始化相关信息
  void Init(const std::string &inputPath, const InputType inputType,
            const uint32_t maxFps = 0, uint64_t maxFrameCount = 0) {
    // 打开解封装句柄
    m_path = inputPath;
    m_inputType = inputType;
    while (lynDemuxOpen(&m_hDemux, inputPath.c_str(), nullptr) != 0) {
      std::this_thread::sleep_for(std::chrono::seconds(4));
      std::cout << "Error in open: " << inputPath << std::endl;
    }
    std::this_thread::sleep_for(std::chrono::seconds(1));
    CHECK_ERR(lynDemuxGetCodecPara(m_hDemux, &m_tDemuxPara));
    lynDemuxGetFrameRate(m_hDemux, &m_inputFps);

    if (maxFps == 0)
      m_maxFps = m_inputFps;
    else
      m_maxFps = maxFps;
    m_needSkipFps = m_inputFps - m_maxFps;
    if (m_needSkipFps < 0) m_needSkipFps = 0;

    // 设置解码帧总数
    m_maxFrameCount = maxFrameCount;

    // 解码参数
    m_tVdecAttr.codecId = m_tDemuxPara.codecId;
    m_tVdecAttr.outputFmt = LYN_PIX_FMT_NV12;
    m_tVdecAttr.userPtrUsed = true;

    m_tVdecAttr.scale = SCALE_NONE;
    CHECK_ERR(lynVdecGetOutInfo(&m_tDemuxPara, &m_tVdecAttr, &m_vdecOutInfo));
    // 打开解码句柄
    CHECK_ERR(lynVdecOpen(&m_hVdecode, &m_tVdecAttr));
  }

  void GetVdecOutInfo(lynVdecOutInfo_t *outInfo) { *outInfo = m_vdecOutInfo; }

  // 反初始化
  void UnInit() {
    if (m_hDemux) {
      CHECK_ERR(lynDemuxClose(m_hDemux));
      m_hDemux = nullptr;
    }
    if (m_hVdecode) {
      CHECK_ERR(lynVdecClose(m_hVdecode));
      m_hVdecode = nullptr;
    }
  }

  // 开启线程
  void Start(lynContext_t ctx, BlockQueue<lynFrame_t *> &frameQueue,
             FramePool &framePool) {
    if (m_pThread == nullptr) {
      // m_bQuit = false;
      m_pThread = new std::thread(&VideoDecoder::Process, this, ctx,
                                  std::ref(frameQueue), std::ref(framePool));
    }
  }

  // 等待线程结束
  void Stop(bool bWait = true) {
    m_bQuit = true;

    if (bWait) {
      Wait();
    }
  }

  // 等待线程结束
  void Wait() {
    if (m_pThread != nullptr) {
      m_pThread->join();
      delete m_pThread;
      m_pThread = nullptr;
    }
  }

 private:
  void ReOpenVdec() {
    CHECK_ERR(lynVdecClose(m_hVdecode));
    m_hVdecode = nullptr;
    CHECK_ERR(lynDemuxGetCodecPara(m_hDemux, &m_tDemuxPara));
    lynDemuxGetFrameRate(m_hDemux, &m_inputFps);
    m_needSkipFps = m_inputFps - m_maxFps;
    if (m_needSkipFps < 0) m_needSkipFps = 0;
    CHECK_ERR(lynVdecGetOutInfo(&m_tDemuxPara, &m_tVdecAttr, &m_vdecOutInfo));
    CHECK_ERR(lynVdecOpen(&m_hVdecode, &m_tVdecAttr));
  }

  // 视频解码线程
  void RecvThread(lynContext_t ctx, BlockQueue<lynFrame_t *> &frameQueue,
                  FramePool &framePool) {
    CHECK_ERR(lynSetCurrentContext(ctx));
    lynStream_t vdecRecvStream = nullptr;
    CHECK_ERR(lynCreateStream(&vdecRecvStream));

    uint64_t num;
    bool bEos = false;
    while (!bEos) {
      bool takeResult = m_sendQueue.take(num, 1000);  // 1s 若拿不到，则忽略；
      if (!takeResult) {
        continue;
      }
      // 发送接收帧
      lynCodecBuf_t *pFrame = framePool.Pop();
      pFrame->size = m_vdecOutInfo.predictBufSize;
      bEos = (num == UINT64_MAX) ? true : false;
      pFrame->eos = bEos;

#ifdef DEMUX_DECODE_TIME  // 解封装后到解码完成的延时时间
      CHECK_ERR(lynVdecRecvFrameAsync(vdecRecvStream, m_hVdecode, pFrame));
      auto decCallback = [&frameQueue, pFrame]() {
        // 通过userPtr传递帧解封装出的时间，计算出解封装后解码延时时间(us)
        uint64_t beginTime = (uint64_t)pFrame->userPtr;
        uint64_t endTime = getCurrentTimestamp();
        pFrame->userPtr = (void *)(endTime - beginTime);
        frameQueue.put(pFrame);
      };
#else  // 计算解码时间，不包含解封装后到解码前消耗的时间
      uint64_t beginTime = getCurrentTimestamp();
      CHECK_ERR(lynVdecRecvFrameAsync(vdecRecvStream, m_hVdecode, pFrame));

      auto decCallback = [&frameQueue, pFrame, beginTime]() {
        uint64_t endTime = getCurrentTimestamp();
        pFrame->userPtr = (void *)(endTime - beginTime);

        frameQueue.put(pFrame);
      };
#endif
      SetCallback(vdecRecvStream, decCallback);
    }
    CHECK_ERR(lynSynchronizeStream(vdecRecvStream));
    CHECK_ERR(lynDestroyStream(vdecRecvStream));
  }

  void sendPacket(lynStream_t vdecSendStream, lynPacket_t *pDmxPacket,
                  int &needSkipFps, bool &needSkip, uint64_t &addr_index) {
    uint64_t beginTime = getCurrentTimestamp();
    pDmxPacket->userPtr = (void *)beginTime;

    if (m_inputType == File) {  // 本地文件直接结束
      CHECK_ERR(lynVdecSendPacketAsync(vdecSendStream, m_hVdecode, pDmxPacket));
    } else if (m_inputType == Stream) {  // 在线流
      if (needSkipFps > 0 && needSkip) {
        CHECK_ERR(lynVdecSendSkippedPacketAsync(vdecSendStream, m_hVdecode,
                                                pDmxPacket));
        --needSkipFps;
      } else {
        CHECK_ERR(
            lynVdecSendPacketAsync(vdecSendStream, m_hVdecode, pDmxPacket));
      }

      {
        int gap = std::ceil(static_cast<double>(m_needSkipFps) / m_maxFps);
        if (addr_index % (gap + 1) == 0) {
          needSkip = false;
        } else {
          needSkip = true;
        }
      }
      ++addr_index;
      if (addr_index >= m_inputFps) {  // 重置跳帧
        addr_index = 0;
        needSkipFps = m_needSkipFps;
      }
    }

    DECODE_DATA_T *pUserData = new DECODE_DATA_T();
    pUserData->pFrame = pDmxPacket;
    CHECK_ERR(lynStreamAddCallback(
        vdecSendStream,
        [](void *pUserData) -> lynError_t {
          lynError_t lRet = 0;
          DECODE_DATA_T *userData = (DECODE_DATA_T *)pUserData;
          if (!userData->pFrame->eos) {
            lRet = lynDemuxFreePacket(userData->pFrame);
          }
          delete userData->pFrame;
          delete userData;
          return lRet;
        },
        pUserData));
    CHECK_ERR(lynSynchronizeStream(vdecSendStream));
  }

  void Process(lynContext_t ctx, BlockQueue<lynFrame_t *> &frameQueue,
               FramePool &framePool) {
    CHECK_ERR(lynSetCurrentContext(ctx));

    // 创建解码发送和接收流
    lynStream_t vdecSendStream = nullptr;
    CHECK_ERR(lynCreateStream(&vdecSendStream));

    auto recvThread = std::thread(&VideoDecoder::RecvThread, this, ctx,
                                  std::ref(frameQueue), std::ref(framePool));

    bool bEos = false;
    bool bMaxFrame = false;
    uint64_t index = 1, addr_index = 0;
    uint64_t indexMax = UINT64_MAX - 10;
    uint64_t frameCount = 0;
    int needSkipFps = m_needSkipFps, retReadPackt = 0;
    bool needSkip = false, reconnect = false;  // 跳帧控制位
    while (!bEos) {
      if (m_maxFrameCount > 0 && frameCount >= m_maxFrameCount) {
        bMaxFrame = true;
      }
      lynPacket_t *pDmxPacket = new lynPacket_t();
      if (m_bQuit || bMaxFrame) {
        bEos = true;
      } else {
        retReadPackt = lynDemuxReadPacket(m_hDemux, pDmxPacket);
        if (retReadPackt != 0) {
          if (m_inputType == File ||
              retReadPackt == lynEDEMUXRESOLUTION) {  // 本地文件直接结束
            bEos = true;
          } else if (m_inputType == Stream) {  // 在线流，尝试重连
            std::cout << "trying to reconnect" << std::endl;
            delete pDmxPacket;
            pDmxPacket = nullptr;
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            CHECK_ERR(lynDemuxClose(m_hDemux));
            m_hDemux = nullptr;
            int ret = 1;
            while (ret) {
              std::cout << "[" << m_path << "] connect again!!!\n";
              std::this_thread::sleep_for(std::chrono::milliseconds(1000));
              ret = (lynDemuxOpen(&m_hDemux, m_path.c_str(), nullptr));
            }
            std::cout << "[" << m_path << "] connect success!!!\n";
            reconnect = true;
            continue;
          }
        }
      }

      pDmxPacket->eos = bEos;

      // 向解码器发送
      if (m_bQuit || bMaxFrame || retReadPackt == lynEDEMUXRESOLUTION || reconnect) {
        // 发送eos帧
        bEos = true;
        pDmxPacket->eos = bEos;
        if (!bMaxFrame) {
          while (m_sendQueue.size() > 0) {
            lynFrame_t *pFrame;
            frameQueue.take(pFrame);
            framePool.Push(pFrame);
          }
          m_sendQueue.clear();
        }

        CHECK_ERR(
            lynVdecSendPacketAsync(vdecSendStream, m_hVdecode, pDmxPacket));
        CHECK_ERR(lynSynchronizeStream(vdecSendStream));

      } else {
        sendPacket(vdecSendStream, pDmxPacket, needSkipFps, needSkip,
                   addr_index);
      }

      if (!bEos) {
        m_sendQueue.put(++index);
        if (index >= indexMax) {
          std::cout << "reset index!\n";
          index = 0;
        }
      } else {
        m_sendQueue.put(UINT64_MAX);
      }

      // 检测到分辨率变化时，frameQueue和framePool进行reset，
      if (retReadPackt == lynEDEMUXRESOLUTION || reconnect) {
        bEos = false;
        m_resolutionchange = true;
        recvThread.join();
        ReOpenVdec();
        framePool.ResetSize(m_vdecOutInfo.predictBufSize);
        recvThread = std::thread(&VideoDecoder::RecvThread, this, ctx,
                                 std::ref(frameQueue), std::ref(framePool));
        // 把I帧再send一遍
        pDmxPacket->eos = bEos;
        needSkipFps = m_needSkipFps;
        needSkip = false;
        addr_index = 0;
        index = 0;
        sendPacket(vdecSendStream, pDmxPacket, needSkipFps, needSkip,
                   addr_index);
        m_sendQueue.put(++index);
        reconnect = false;
      }

      frameCount++;
    }

    // 视频处理完后退出线程并清理相关资源
    CHECK_ERR(lynSynchronizeStream(vdecSendStream));
    CHECK_ERR(lynDestroyStream(vdecSendStream));
    recvThread.join();
  }

 public:
  bool m_resolutionchange = false;

 private:
  std::atomic_bool m_bQuit;  // 退出标识
  BlockQueue<uint64_t> m_sendQueue{UINT64_MAX};
  lynDemuxHandle_t m_hDemux = nullptr;       // 解封装句柄
  lynCodecPara_t m_tDemuxPara;     // 解封装参数
  lynVdecHandle_t m_hVdecode = nullptr;      // 解码句柄
  lynVdecAttr_t m_tVdecAttr;       // 解码参数
  lynVdecOutInfo_t m_vdecOutInfo;  // 解码输出信息
  std::thread *m_pThread;          // 处理线程
  std::string m_path;
  InputType m_inputType;
  std::mutex m_mutex;
  uint32_t m_maxFps;
  uint32_t m_inputFps;
  uint32_t m_needSkipFps = 0;  // 需要跳帧的帧数
  uint64_t m_maxFrameCount = 0;
};
